/*
 * DocumentHandler.java
 * 
 * Copyright (c) 2010 Jaymie Strecker (strecker at cs.umd.edu). All rights reserved.
 * 
 * This file is part of Tonic.
 * 
 * Tonic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Tonic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Tonic.  If not, see <http://www.gnu.org/licenses/>.
 */

package tonic.gui;

import java.awt.Component;
import java.awt.Cursor;
import java.io.File;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import tonic.audio.Document;
import tonic.audio.Player;

/**
 * DocumentHandler provides methods for saving and opening audio files and documents. When opening
 * documents, it is responsible for updating the state of audio objects but not the GUI. 
 *
 * @author Jaymie Strecker (strecker at cs.umd.edu)
 */
public class DocumentHandler {
    private static final JFileChooser openChooser = new JFileChooser(System.getProperty("user.dir")),
            audioChooser = new JFileChooser(System.getProperty("user.dir")),
            documentChooser = new JFileChooser(System.getProperty("user.dir"));
    static {
        openChooser.setFileFilter(new AudioAndDocumentFileFilter());
        audioChooser.setFileFilter(new AudioFileFilter());
        documentChooser.setFileFilter(new DocumentFileFilter());
    }
    private static final String DOC_EXTENSION = "tonic";

    /**
     * Uninstantiable.
     */
    private DocumentHandler() {
        // nop
    }

    /**
     * Shows a file chooser, opens the chosen file, and updates the state of audio objects.
     * The chosen file may be either an audio file or a saved file. 
     *
     * @param parent    the parent component to use when displaying dialogs
     * @return  the chosen file if it opens successfully, otherwise null
     */
    public static File open(Component parent) {
        int ret = openChooser.showOpenDialog(parent);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = openChooser.getSelectedFile();
            String extension = extension(file.getName());

            if (extension.equals(DOC_EXTENSION)) {
                // Saved document - If it can't be opened, display an error.
                // If the audio file given in the saved document can't be opened,
                // give the user a chance to find the file.
                Document doc = Document.getEmptyDocument();
                try {
                    doc.read(file);
                } catch (IOException e) {
                    new ErrorDisplay(parent,
                            "Could not open file " + file.getAbsolutePath(),
                            e, false);
                    return null;
                }
                try {
                    parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                    doc.setAudioFile();
                    doc.setExcerptList();
                    return doc.getAudioFile();
                } catch (IOException e) {
                    String newLine = System.getProperty("line.separator");
                    String message = "There was a problem opening the audio file " + newLine
                            + "\t" + doc.getAudioFile() + newLine
                            + " referenced in file " + newLine
                            + "\t" + file.getAbsolutePath() + newLine
                            + "Either the audio file was not available or "
                            + "its content has changed since the document was saved. " + newLine
                            + "You may use the next dialog to locate the audio file. ";
                    JOptionPane.showMessageDialog(parent, message, "", JOptionPane.PLAIN_MESSAGE);
                    ret = audioChooser.showOpenDialog(parent);
                    if (ret == JFileChooser.APPROVE_OPTION) {
                        File audioFile = audioChooser.getSelectedFile();
                        try {
                            doc.setAudioFile(audioFile);
                            doc.setExcerptList();
                            return audioFile; 
                        } catch (IOException ex) {
                            message = "Could not open audio file " + newLine
                                    + "\t" + doc.getAudioFile() + newLine
                                    + "corresponding to file " + newLine
                                    + "\t" + file.getAbsolutePath(); 
                            new ErrorDisplay(parent, message, ex, false);
                            return null;
                        }
                    } else {
                        return null;
                    }
                } finally {
                    parent.setCursor(Cursor.getDefaultCursor());
                }

            } else {
                // Audio file - If it can't be opened, display an error.
                try {
                    parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                    Player.setFile(file);
                    return file;
                } catch (Throwable e) {
                    // TODO Can the JAR file specify the heap size?
                    // TODO Handle large files more smoothly. Loading is slow, playing is jerky.
                    //      (It helps somewhat to increase the memory.)
                    String path = (file == null) ? "null" : file.getAbsolutePath();
                    String message = "Could not open file at path " + path
                            + System.getProperty("line.separator") + e.getMessage();
                    if (e instanceof java.lang.OutOfMemoryError) {
                        message += System.getProperty("line.separator") + System.
                                getProperty("line.separator")
                                + "The memory allocated to this program is not enough to load this file. "
                                + "If you know how to run a jar file on the command line, try this: "
                                + System.getProperty("line.separator")
                                + "   java -Xmx768m -jar tonic.jar"
                                + System.getProperty("line.separator")
                                + "Substitute the correct name for the .jar file and, if necessary, "
                                + "an even larger number than 768.";
                    }
                    new ErrorDisplay(parent, message, e, true);
                    // Recommend restart because, when you attempt to open 2 large files in a row,
                    // you get an uncaught "out of heap space" exception on the 2nd one.
                    // (TODO - Is the above still true?)
                    return null;
                } finally {
                    parent.setCursor(Cursor.getDefaultCursor());
                }
            }
        } else {
            return null;
        }
     }

    /**
     * Shows a file chooser and saves to the chosen file. 
     *
     * @param parent    the parent component to use when displaying dialogs
     */
    public static void save(Component parent) {
        Document doc = Document.getCurrentDocument(); 
        int ret = documentChooser.showSaveDialog(parent);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = documentChooser.getSelectedFile(); 
            try {
                if (! extension(file.getName()).equals(DOC_EXTENSION)) {
                    file = new File(file.getAbsolutePath() + "." + DOC_EXTENSION);
                }
                doc.save(file);
            } catch (IOException e) {
                new ErrorDisplay(parent, "Could not save file " + file.getAbsolutePath(),
                        e, false);
            }            
        }
    }

    /**
     * File filter for audio files and saved document files.
     */
    private static class AudioAndDocumentFileFilter extends FileFilter {

        public boolean accept(File f) {
            if (f.isDirectory()) {
                return true;
            }
            String extension = extension(f.getName());
            return extension.equals("wav") || extension.equals(DOC_EXTENSION);
        }

        public String getDescription() {
            return ".wav and ." + DOC_EXTENSION + " files";
        }
    }

    /**
     * File filter for audio files.
     */
    private static class AudioFileFilter extends FileFilter {

        public boolean accept(File f) {
            if (f.isDirectory()) {
                return true;
            }
            String extension = extension(f.getName());
            return extension.equals("wav");
        }

        public String getDescription() {
            return ".wav files";
        }
    }

    /**
     * File filter for saved document files.
     */
    private static class DocumentFileFilter extends FileFilter {

        public boolean accept(File f) {
            if (f.isDirectory()) {
                return true;
            }
            String extension = extension(f.getName());
            return extension.equals(DOC_EXTENSION);
        }

        public String getDescription() {
            return "." + DOC_EXTENSION + " files";
        }
    }

    private static String extension(String fileName) {
        int dotIndex = fileName.lastIndexOf('.');
        String extension;
        if (dotIndex < 0) {
            extension = "";
        } else {
            extension = fileName.substring(dotIndex + 1);
        }
        return extension; 
    }

}
